--[[
	其实就是skynet基本例子中的 watchdog
]]
local filename = "loginnode.lua"

local skynet = require "skynet"
local netpack = require "skynet.websocketnetpack"
require "skynet.manager"

local filelog = require "filelog"
local area_conf = require "areaconf"
local iptool = require "iptool"
local timetool = require "timetool"
local redisdao = require "redisdao"

local json = require "cjson"
json.encode_sparse_array(true,1,1)

local Loginnode = {
	gate_service = nil,
	service_manager = {},
	protobuf = nil, 
	decode = nil,
	netpack = nil,
	socket = nil,
	tcpmng = nil,
	eventpool = {},
    ip_list = {},           --1分钟内登录ip列表
    ip_list_len = 0,        --登录ip列表长度
    limit_login = false,    --是否限制登录，为true奖关闭登录
    user_list = {},         --用户列表，保存修改密码用户
    limit_users = {},       --限制的user列表，一分钟内3次登录失败，十分钟后才能登录
    vercode_ips = {},       --获取验证码的ip列表
}

function Loginnode:process(session, source, calltype, msgname, ...)
	if msgname == nil then
		filelog.sys_error(filename.." [BASIC_EVENTMNG] invalid msgname")
		return
	end
	filelog.sys_protomsg("line29__" .. skynet.self().."__"..calltype.."_"..msgname.."_request", ...)
	local f = self.eventpool[msgname]
	if f == nil then
		filelog.sys_error(filename.." [BASIC_EVENTMNG] invalid msgname:"..msgname, ...)		
		return
	end
	local status, result = skynet.pcall(f.process, session, source, ...)
	if not status then
		filelog.sys_error(filename.." [BASIC_EVENTMNG] skynet.pcall failed", msgname, result, ...)
	end
end

-- 有错误消息的时候，也有只发送消息头，不发送消息体的时候
function Loginnode:send_resmsgto_client(fd, msgname, msgbody, retcode, retstr)
	self.socket.write(fd, netpack.pack(self.decode.s2cpack(self.protobuf, msgname, retcode, retstr, msgbody)))
end

--检测ip位置信息
function Loginnode:check_locationinfo(ip)
    --暂不检测位置
    if true then
        return true
    end

    local info = iptool.IpLocation(ip) or {}
    local openarea_list = area_conf.openarea_list or {}
    local closearea_list = area_conf.closearea_list or {}

    info.country = info.country or ""
    info.province = info.province or ""
    info.city = info.city or ""

    --检测黑名单
    local blist = closearea_list[info.country]
    if blist then
        if blist[info.province] or blist[info.city] 
        or blist[(info.province .. "." .. info.city)] then
            return false
        end
    end

    --检测白名单
    local wlist = openarea_list[info.country]
    if wlist then
        if wlist[info.province] or wlist[info.city] 
        or wlist[(info.province .. "." .. info.city)] then
            return true
        end
        return false
    end
    return true
end

--刷新ip数据库
function Loginnode:reload_ipdata(is_all)
    iptool.init()
    if is_all then
        local fp = io.open("areaconf.lua","r")
	    if fp then
		    data = fp:read("*a")
            local result = load(data,"error file!")()
            if type(result) == "table" then
                area_conf = result
            end
            fp:close()
	    end
    end
end

--检测是否为限制ip
function Loginnode:check_limit_ip(ip)
    --跳过内网ip
    local ip1, ip2, ip3, ip4 = string.match(ip, "(%d+).(%d+).(%d+).(%d+)")
    if true or ip == "127.0.0.1" or (ip1 == "192" and ip2 == "168") then
        return false
    end

    local now = os.time()
    local limit_time = 300   --300
    local last_time = self.ip_list[ip] or 0

    if (now - last_time) <= limit_time then
        return true
    else
        if not self.ip_list[ip] then
            self.ip_list_len = self.ip_list_len + 1
        end
        self.ip_list[ip] = now
    end
    if self.ip_list_len > 2000 then
        self.limit_login = true
    end
    if self.ip_list_len > 200 then
        for k,v in pairs(self.ip_list) do
            if (now - v) > limit_time then
                self.ip_list[k] = nil
                self.ip_list_len = self.ip_list_len - 1
            end
        end
    else
        self.limit_login = false
    end

    return false
end

--限制用户登录
function Loginnode:hanlde_limit_users(username, is_rm)
    if not username or username == "" then
        return
    end
    if is_rm then
        Loginnode.limit_users[username] = nil
        return
    end
    Loginnode.limit_users[username] = Loginnode.limit_users[username] or {}

    local outtime = 60  --60s
    local failed_num = 3--3次
    local curtime = os.time()
    local userinfo = Loginnode.limit_users[username]
    userinfo.start_time = userinfo.start_time or curtime
    userinfo.failed_num = (userinfo.failed_num or 0) + 1

    local diff_value = curtime - userinfo.start_time
    if diff_value <= outtime and userinfo.failed_num >= failed_num then
        userinfo.start_time = nil
        userinfo.failed_num = nil
        userinfo.is_locked = true
        userinfo.locked_time = curtime + 1800   --冻结30分钟
    elseif diff_value > outtime then
        userinfo.start_time = curtime
        userinfo.failed_num = 1
    end

end

--检测是否为限制用户
function Loginnode:check_limit_users(username)
    username = username or ""

    local curtime = os.time()
    local userinfo = Loginnode.limit_users[username] or {}
    if userinfo.is_locked then
        if curtime <= userinfo.locked_time then
            return true
        else
            userinfo = nil
        end
    end

    return false
end

--检测手机登录验证码
function Loginnode:check_ver_code(phonenum, verification_code)
    --跳过超级账号
    if phonenum == "17633763321" and verification_code == "763321" then
        return {}
    end
    
    --检测手机号
    local id = phonenum or ""
    if not self.user_list[id] or self.user_list[id].cur_verif_phonenum ~= id then
         return {retcode = EErrCode.ERR_INVALID_REQUEST,retstr = "错误的手机号或验证码"}
    end

    --验证码为空，尝试从redis读取一次
    local key = id or 0
    if not self.user_list[id].cur_verif_code then
        local status,data = redisdao.query_data(".gmmqredis", "get", key .. ":cur_verifinfo")
        local cur_verifinfo = json.decode(data) or { }
        if cur_verifinfo then
            self.user_list[id].cur_verif_phonenum = tostring(cur_verifinfo.cur_verif_phonenum)
            self.user_list[id].cur_verif_code = tostring(cur_verifinfo.cur_verif_code)
            self.user_list[id].cur_verif_code_expiratetime = tonumber(cur_verifinfo.cur_verif_code_expiratetime)
        end
    end

    if not self.user_list[id].cur_verif_phonenum or not self.user_list[id].cur_verif_code 
    or self.user_list[id].cur_verif_phonenum ~= phonenum then
        return {retcode = EErrCode.ERR_INVALID_REQUEST,retstr = "错误的验证码"}
    end

    local verif_code = verification_code
    local cur_time = timetool.get_time()
    if verif_code == "" or verif_code ~= self.user_list[id].cur_verif_code then
        self.user_list[id].userinfo = self.user_list[id].userinfo or {}
        local userinfo = self.user_list[id].userinfo
        if userinfo.verif_code ~= self.user_list[id].cur_verif_code then
            userinfo.failed_num = nil
            userinfo.start_time = nil
            userinfo.verif_code = self.user_list[id].cur_verif_code
            userinfo.expiratetime = self.user_list[id].cur_verif_code_expiratetime
        end
        userinfo.failed_num = (userinfo.failed_num or 0) + 1
        userinfo.start_time = userinfo.start_time or cur_time

        return {retcode = EErrCode.ERR_INVALID_REQUEST,retstr = "错误的验证码"}
    end

    if cur_time > self.user_list[id].cur_verif_code_expiratetime then
        return {retcode = EErrCode.ERR_INVALID_REQUEST,retstr = "验证码已过期,请重新获取"}
    end

    --1个验证码最多尝试5次
    local userinfo = self.user_list[id].userinfo
    if userinfo and userinfo.failed_num >= 5 and userinfo.expiratetime == self.user_list[id].cur_verif_code_expiratetime then
        return {retcode = EErrCode.ERR_INVALID_REQUEST,retstr = "验证码错误次数过多，请重新获取"}
    end
    self.user_list[id] = nil

    --删除redis记录
    redisdao.query_data(".gmmqredis", "del", key .. ":cur_verifinfo")

	return {}
end

function Loginnode:start()
	skynet.register_protocol {
		name = "client",
		id = skynet.PTYPE_CLIENT,
	}

	-- 重置protobuf解析器到全局唯一的解析器地址
	debug.getregistry().PROTOBUF_ENV = skynet.call(".pbcservice", "lua", "get_protobuf_env")
	self.protobuf = require "protobuf"
	self.decode = require "protopack"

	self.socket = require("skynet.socket")
	self.gate_service = skynet.newservice("wsgate")
	self.service_manager["wsgate"] = self.gate_service
	self.tcpmng = require("short_tcp_mng")		
	skynet.dispatch("lua", function (session, source, ...)
							Loginnode:process(session, source,"lua", ...)
							 end)
	self:init()
	skynet.register(skynet.getenv("nodename"))
end

function Loginnode:add_eventbyname(name, event)
	self.eventpool[name] = require(event)
	self.eventpool[name].service = Loginnode
end

function  Loginnode:init()
	self:add_eventbyname("cmd", "nodemsg/loginnodecmd")
	self:add_eventbyname("socket", "nodemsg/loginnodesocket")
    self:add_eventbyname("getverificationcode", "nodemsg/getverificationcode")
    --self:add_eventbyname("changephonepassword", "nodemsg/changephonepassword")
end

skynet.start(function()
	Loginnode:start()
end)
